Playwrightのパラメータ付きFixtureで効率的なテストパターン管理を実現する
情報システム室の進地@日比谷です。
先日書いた次の記事で、PlaywrightにてFixtureを使うことで画面状態を共有、再利用することができることをご案内しました。
Playwright fixture 活用術:テスト間での画面状態の再利用パターン
しかし、再利用の観点からすると、Fixtureに対してパラメータを渡したいということはままある要望かと思います。例えば、検索ワードが違うだけの検索結果画面をそれぞれチェックする場合など、検索ワードをパラメータとしてFixtureに渡せると嬉しいです。
そこで、Fixtureにパラメータを渡す方法として、パラメータ付きFixtureを実装する方法をご紹介します。
パラメータを受ける関数を返すFixtureを定義する
Fixtureでは、パラメータを受け取って処理したPageオブジェクトを返す非同期関数
を返すように定義します。
次のコード例では検索キーワードをパラメータで受け取り、検索結果のPageオブジェクトを返す非同期関数を記述し、それをawait use()
に与えることでパラメータ付きFixtureを定義しています。
// tests/fixtures/search.ts
import { test as base } from '@playwright/test';
import { Page } from '@playwright/test';
// カスタムフィクスチャの型定義
type CustomFixture = {
searchedPage: (keyword: string) => Promise<Page>;
};
// フィクスチャの拡張
export const test = base.extend<CustomFixture>({
// 検索結果の状態を得る
searchedPage: await async ({ page }, use) => {
await use(async (keyword: string) => {
// テスト対象のページへアクセスする
await page.goto('https://example.com/example.html');
// 例)パラメータで受け取ったkeywordで検索実行する
await page.fill('#keyword', keyword);
await page.getByRole('button', { name: '検索する' }).click();
return page;
});
}
});
export { expect } from '@playwright/test';
このFixtureの使い方は次の通りです。
import { test, expect } from './fixtures/search';
// testを並列実行する
test.describe.configure({ mode: 'parallel' });
test.describe('検索結果に対するテスト群', () => {
test('タイトルに「クラスメソッド」が含まれている', async ({ searchedPage }) => {
const classmethodSearchedPage = await searchedPage('クラスメソッド');
expect.soft(await classmethodSearchedPage.locator('.title'), `「クラスメソッド」が見つかりません`).toHaveText(/クラスメソッド/);
});
test('タイトルに「アノテーション」が含まれている', async ({ searchedPage }) => {
const annotationSearchedPage = await searchedPage('アノテーション');
expect.soft(await annotationSearchedPage.locator('.title'), `「アノテーション」が見つかりません`).toHaveText(/アノテーション/);
});
});
Pageオブジェクト以外の複雑なオブジェクトも返せる
パラメータ付きFixtureには、通常のFixtureと同様に様々な複雑な構造のオブジェクトも返すことができます。
例えば、検索結果を表す値オブジェクトをSearchResultとして定義して、それを返すこともできます。
// tests/models/SearchResult.ts
export class SearchResult {
private constructor(
private readonly titles: readonly string[]
) {
this.titles = titles;
}
// ファクトリーメソッド
static create(titles: string[]): SearchResult {
return new SearchResult(titles);
}
// ゲッター
getTitles(): readonly string[] {
return this.titles;
}
}
このオブジェクトを返すパラメータ付きFixtureは例えば次のように定義します。
// tests/fixtures/search.ts
import { test as base } from '@playwright/test';
import { Page } from '@playwright/test';
// カスタムフィクスチャの型定義
type CustomFixture = {
searchedPage: (keyword: string) => Promise<SearchResult>;
};
// フィクスチャの拡張
export const test = base.extend<CustomFixture>({
// 検索結果の状態を得る
searchedPage: await async ({ page }, use) => {
await use(async (keyword: string) => {
// テスト対象のページへアクセスする
await page.goto('https://example.com/example.html');
// 例)パラメータで受け取ったkeywordで検索実行する
await page.fill('#keyword', keyword);
await page.getByRole('button', { name: '検索する' }).click();
// 例)検索結果からリンクテキストを取得(検索結果がテーブル組で表示されていると仮定)
const anchors = await page.locator('tbody tr td a').all();
const titles: string[] = [];
for await (const anchor of anchors) {
titles.push(await anchor.textContent());
}
// SearchResultオブジェクトを返す
return SearchResult.create(titles);
});
}
});
export { expect } from '@playwright/test';
使い方は次の通りです。
import { test, expect } from './fixtures/search';
// testを並列実行する
test.describe.configure({ mode: 'parallel' });
test.describe('検索結果に対するテスト群', () => {
test('「クラスメソッド」の検索結果が10件ある', async ({ searchedPage }) => {
const classmethodSearchResult: SearchResult = await searchedPage('クラスメソッド');
expect.soft(classmethodSearchResult.getTitles().length, `「クラスメソッド」の検索結果が10件ではありません`).toBe(10);
});
test('「アノテーション」の検索結果が5件ある', async ({ searchedPage }) => {
const annotationSearchResult: SearchResult = await searchedPage('アノテーション');
expect.soft(annotationSearchResult.getTitles().length, `「アノテーション」の検索結果が5件ではありません`).toBe(5);
});
});
まとめ
パラメータ付きFixtureを使うことでE2Eテストにおける同一、類似操作の集約や再利用などをさらに進めることができました。テストコードの保守性の向上に繋がりますのでぜひ活用してみてください。